package se.vgregion.icc.riv.auth;

import java.text.MessageFormat;
import java.util.Arrays;
import java.util.List;

import org.apache.cxf.common.util.StringUtils;
import org.mule.api.MuleMessage;
import org.mule.api.context.MuleContextAware;
import org.mule.api.transformer.TransformerException;
import org.mule.api.transport.PropertyScope;
import org.mule.transformer.AbstractMessageTransformer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.soitoolkit.commons.mule.cert.X509CertificateEvaluator;

/**
 * Validates sender-id in X509 client certificate.<br/>
 * <br/>
 * Sample configuration:<br/>
 * {@code <custom-transformer name="validateSenderId" class="se.vgregion.icc.riv.auth.ValidateSenderIdTransformer">}
 * <br/>
 * &nbsp;&nbsp;&nbsp;&nbsp;{@code <spring:property name="senderIdPropertyName" value="OU"/>}<br/>
 * &nbsp;&nbsp;&nbsp;&nbsp;{@code <spring:property name="validSenderIds"       value="CGM-ES,SIE"/>}<br/>
 * {@code </custom-transformer>}<br/>
 *
 * @see {@link org.mule.transformer.AbstractMessageTransformer}
 * @see {@link org.mule.api.context.MuleContextAware}
 */
public class ValidateSenderIdTransformer extends AbstractMessageTransformer implements MuleContextAware {

    /** Name of the Mule message session property used as identifier of the authenticated sender. */
    private static final String SENDER_PROPERTYNAME = "Sender";

    protected static final String TRANSFORMER_CONFIGURATION_ERROR_MESSAGE =
        "Transformer configuration error, missing required property";

    private static final Logger LOGGER = LoggerFactory.getLogger(ValidateSenderIdTransformer.class);

    /** The name of the property in the certificate used to identify the sender, e.g. "CN", "OU"
     * For a SITHS certificate this should be "2.5.4.5"
     */
    private String senderIdPropertyName;

    /**
     * The list of identifiers for authenticated senders.
     */
    private List<String> validSenderIds;

    private X509CertificateEvaluator x509evaluator;

    /**
     * Property senderIdPropertyName.
     * @param senderIdPropertyName The name of the sender identification property to lookup sender id inside the
     * client certificate.
     */
    public void setSenderIdPropertyName(String senderIdPropertyName) {
        this.senderIdPropertyName = senderIdPropertyName;
    }

    /**
     * Setter for property validSenderIds.
     * @param validSenderIds List of identifiers of the authenticated senders, used for authentication of
     * the current client sender-id.
     */
    public void setValidSenderIds(String validSenderIds) {
        if (validSenderIds != null) {
            this.validSenderIds = Arrays.asList(validSenderIds.split(","));
        }
    }

    /**
     * Getter for the x509evaluator field, that also sets property name of the certificate principal property holding
     * the sender id.
     * 
     * @param property The property name of the sender id
     * @return the X509CertificateEvaluator instance.
     */
    protected X509CertificateEvaluator getX509CertificateEvaluator(String property) {
        if (this.x509evaluator == null) {
            this.x509evaluator = new X509CertificateEvaluator();
        }
        this.x509evaluator.setPropertyName(senderIdPropertyName);
        return this.x509evaluator;
    }

    /**
     * Setter for the X509CertificateEvaluator, provided to enable dependency injection in unit testing.
     * @param evaluator the X509CertificateEvaluator to set.
     * 
     */
    protected void setX509CertificateEvaluator(X509CertificateEvaluator evaluator) {
        this.x509evaluator = evaluator;
    }

    /**
     * Validates the clients certificate sender id property against the list of authenticated senders.
     * Sets the clients sender id  to the SESSION property named "Sender" for further reference in the Mule flow.
     * Throws an exception if the sender id is not in the list of authenticated senders.
     * 
     * @param message The inbound mule message to be evaluated.
     * @param outputEncoding The encoding used in the message returned by the transformer.
     * 
     * @return The transformed message.
     * @throws TransformerException if the client certificate cannot be validated as an authenticated sender.
     */
    @Override
    public Object transformMessage(MuleMessage message, String outputEncoding) throws TransformerException {

        /* Validate that the tranformer is properly configured. */
        validateTransformerConfiguration();

        X509CertificateEvaluator eval = getX509CertificateEvaluator(senderIdPropertyName);

        String senderId = (String) eval.evaluate(X509CertificateEvaluator.EXPR_SENDERID, message);

        if (senderId == null) {
            senderId = "";
        }

        /* add sender-id to message header */
        message.setProperty(SENDER_PROPERTYNAME, senderId, PropertyScope.SESSION);

        if (validSenderIds.contains(senderId)) {
            LOGGER.debug("{} is a valid senderId!", senderId);
        } else {
            String errorMessage = MessageFormat.format("Invalid senderId [{0}]", senderId);
            String logMessage = MessageFormat.format("{0}, allowed senderIds are: {1}", errorMessage, validSenderIds);
            logger.error(logMessage);
            throw new TransformerException(this, new RuntimeException(errorMessage));
        }

        return message;
    }

    /**
     * TODO: consider @required for spring configuration validation.
     * @throws TransformerException
     */
    private void validateTransformerConfiguration() throws TransformerException {
        if (StringUtils.isEmpty(senderIdPropertyName)) {
            throw new TransformerException(this, new RuntimeException(TRANSFORMER_CONFIGURATION_ERROR_MESSAGE
                + " [senderIdPropertyName]"));
        }

        if (StringUtils.isEmpty(validSenderIds)) {
            throw new TransformerException(this, new RuntimeException(TRANSFORMER_CONFIGURATION_ERROR_MESSAGE
                + " [validSenderIds]"));
        }

    }

}